package file

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"chainmaker.org/chainmaker/smarteditor/conf"
	"chainmaker.org/chainmaker/smarteditor/session"
	"chainmaker.org/chainmaker/smarteditor/util"

	"github.com/88250/gulu"
)

// SearchTextHandler handles request of searching files under the specified directory with the specified keyword.
func SearchTextHandler(w http.ResponseWriter, r *http.Request) {
	httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
	if httpSession.IsNew {
		http.Error(w, "Forbidden", http.StatusForbidden)

		return
	}

	result := gulu.Ret.NewResult()
	defer gulu.Ret.RetResult(w, r, result)

	var args map[string]interface{}

	if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
		log.Error(err)
		result.Code = -1

		return
	}

	sid := args["sid"].(string)
	wSession := session.WideSessions.Get(sid)
	if nil == wSession {
		result.Code = -1

		return
	}

	// XXX: just one directory

	dir := args["dir"].(string)
	if "" == dir {
		userWorkspace := conf.GetUserWorkspace(wSession.UserID)
		workspaces := filepath.SplitList(userWorkspace)
		dir = workspaces[0]
	}

	extension := args["extension"].(string)
	text := args["text"].(string)

	founds := make([]*Snippet, 0)
	if gulu.File.IsDir(dir) {
		founds = search(dir, extension, text, []*Snippet{})
	} else {
		founds = searchInFile(dir, text)
	}
	result.Data = founds
}

// search finds file under the specified dir and its sub-directories with the specified text, likes the command 'grep'
// or 'findstr'.
func search(dir, extension, text string, snippets []*Snippet) []*Snippet {
	if !strings.HasSuffix(dir, conf.PathSeparator) {
		dir += conf.PathSeparator
	}

	f, _ := os.Open(dir)
	fileInfos, err := f.Readdir(-1)
	util.CloseAndLogError(f)

	if nil != err {
		log.Errorf("Read dir [%s] failed: [%s]", dir, err.Error())
		return snippets
	}

	for _, fileInfo := range fileInfos {
		path := dir + fileInfo.Name()

		if fileInfo.IsDir() {
			// enter the directory recursively
			snippets = search(path, extension, text, snippets)
		} else if strings.HasSuffix(path, extension) {
			// grep in file
			ss := searchInFile(path, text)
			snippets = append(snippets, ss...)
		}
	}
	return snippets
}

// searchInFile finds file with the specified path and text.
func searchInFile(path string, text string) []*Snippet {
	ret := make([]*Snippet, 0)
	bytes, err := ioutil.ReadFile(path)
	if nil != err {
		log.Errorf("Read file [%s] failed: [%s]", path, err.Error())
		return ret
	}
	content := string(bytes)
	if gulu.File.IsBinary(content) {
		return ret
	}
	lines := strings.Split(content, "\n")
	for idx, line := range lines {
		ch := strings.Index(strings.ToLower(line), strings.ToLower(text))
		if -1 != ch {
			snippet := &Snippet{Path: filepath.ToSlash(path),
				Line: idx + 1, Ch: ch + 1, Contents: []string{line}}
			ret = append(ret, snippet)
		}
	}
	return ret
}
